home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / bipl.zip / PROGS.ZIP / POST.ICN < prev    next >
Text File  |  1993-01-27  |  12KB  |  363 lines

  1. ############################################################################
  2. #
  3. #    File:     post.icn
  4. #
  5. #    Subject:  Program to post news
  6. #
  7. #    Author:   Ronald Florence
  8. #
  9. #    Date:     2 October 1991
  10. #
  11. ###########################################################################
  12. #
  13. #    Version:  1.5
  14. #
  15. ###########################################################################
  16. #
  17. #  This program posts a news article to Usenet.  Given an optional
  18. #  argument of the name of a file containing a news article, or an
  19. #  argument of "-" and a news article via stdin, post creates a
  20. #  follow-up article, with an attribution and quoted text.  The
  21. #  newsgroups, subject, distribution, follow-up, and quote-prefix can
  22. #  optionally be specified on the command line.
  23. #
  24. #      usage: post [options] [article | -]
  25. #        -n newsgroups
  26. #        -s subject
  27. #        -d distribution
  28. #        -f followup-to
  29. #        -p quote-prefix (default ` > ')
  30. #
  31. #  See the site & system configuration options below.  On systems
  32. #  posting via inews, post validates newsgroups and distributions in
  33. #  the `active' and `distributions' files in the news library directory.
  34. #
  35. ############################################################################
  36. #
  37. #  Link: options
  38. #
  39. ############################################################################
  40. #
  41. #  Bugs: Newsgroup validation assumes the `active' file is sorted.
  42. #        Non-UNIX sites need hardcoded system information.
  43. #
  44. ############################################################################
  45.  
  46. link options
  47.  
  48. global mode, sysname, domain, tz, tmpfile, opts, console, newslib, org
  49.  
  50. procedure main(arg)
  51.   local usage, smarthost, editor, default_distribution, generic_from
  52.   local tmpdir, logname, fullname, sigfile, article, inf, edstr, outf, tmp2
  53.  
  54.   usage := ["usage: post [options] [article]",
  55.             "\t-n newsgroups",
  56.             "\t-s subject",
  57.             "\t-d distribution",
  58.             "\t-f followup-to",
  59.             "\t-p quote-prefix (default ` > ')",
  60.             "\t-  read article from stdin"]
  61.  
  62.     # Site configuration.   Mode can be
  63.     #   "local" (post via inews),
  64.     #   "uux" (post via rnews to an upstream host),
  65.     #   "mail" (post via mail to an upstream host).
  66.     # For either uux or mail mode,
  67.     #   smarthost := the uucp nodename of the upstream news feed.
  68.       # Use generic_from to force a generic address instead
  69.         #   of the hostname provided by system commands.
  70.  
  71.   mode := "local"        
  72.   smarthost := ""
  73.   editor := "vi"
  74.   domain := ".UUCP"
  75.   default_distribution := "world" 
  76.   generic_from := &null
  77.  
  78.     # For UNIX, the rest of the configuration is automatic.
  79.  
  80.   if find("UNIX", &features) then {
  81.     console := "/dev/tty"
  82.     newslib := "/usr/lib/news/"
  83.     tz := "unix"
  84.     tmpdir := "/tmp/"
  85.     logname := pipe("logname")
  86.     sysname := trim(pipe("hostname", "uname -n", "uuname -l"))
  87.     # BSD  passwd: `:fullname[,...]:'
  88.     # SysV passwd: `-fullname(' 
  89.     \logname & every lookup("/etc/passwd") ? {
  90.       =(logname) & {
  91.     every tab(upto(':')+1) \4 
  92.     fullname := (tab(upto('-')+1), tab(upto('(:'))) | tab(upto(',:'))
  93.     break
  94.       }    
  95.     }
  96.     sigfile := getenv("HOME") || "/.signature"
  97.   }
  98.  
  99.     # For non-UNIX systems, we need hard coded configuration:
  100.     # console := the system's name for the user's terminal.
  101.     # libdir := the directory for news configuration files, like 
  102.     #           an `organization' file.
  103.     # tmpdir := optional directory for temporary files; terminated 
  104.     #        with the appropriate path separator: `/' or `\\'.
  105.     # logname := user's login name.
  106.     # tz := local time zone (e.g., EST).
  107.         # fullname := user's full name.
  108.         # sigfile := full path of file with user's email signature.
  109.  
  110.   else {
  111.     console := "CON"
  112.     newslib := ""
  113.     tmpdir := ""
  114.     logname := &null
  115.     tz := &null
  116.     fullname := &null 
  117.     sigfile := &null
  118.     sysname := getenv("HOST") | &host
  119.   }
  120.  
  121.     # End of user configuration.
  122.  
  123.   (\logname & \sysname & \tz & (mode == "local" | *smarthost > 0)) |
  124.     stop("post: missing system information")
  125.   opts := options(arg, "n:s:d:f:p:h?") 
  126.   \opts["h"] | \opts["?"] | arg[1] == "?" & { 
  127.     every write(!usage)
  128.     exit(-1) 
  129.   }
  130.   org := getenv("ORGANIZATION") | lookup(newslib || "organization")
  131.   article := open(tmpfile := tempname(tmpdir), "w") | 
  132.     stop("post: cannot write temp file")
  133.   write(article, "Path: ",  sysname, "!", logname)
  134.   writes(article, "From: ", logname, "@", \generic_from | sysname, domain)
  135.   \fullname & writes(article, " (", fullname, ")")
  136.   write(article)
  137.  
  138.     # For a follow-up article, reply_headers() does the work. 
  139.  
  140.   if \arg[1] then {
  141.     inf := (arg[1] == "-" & &input) | 
  142.       open(arg[1]) | (remove(tmpfile) & stop("post: cannot read " || arg[1]))
  143.     reply_headers(inf, article)
  144.     every write(article, \opts["p"] | " > ", !inf)
  145.     close(inf)
  146.   }
  147.  
  148.     # Query if newsgroups, subject, and distribution have
  149.         # not been specified on the command line.
  150.  
  151.   else {
  152.     write(article, "Newsgroups: ", 
  153.       validate(\opts["n"] | query("Newsgroups: "), "active"))
  154.     write(article, "Subject: ", \opts["s"] | query("Subject: "))
  155.     write(article, "Distribution: ", 
  156.       validate(\opts["d"] | query("Distribution: ", default_distribution), 
  157.            "distributions"))
  158.     every write(article, req_headers())
  159.     write(article, "\n")
  160.   }
  161.   close(article)
  162.   edstr := (getenv("EDITOR") | editor) || " " || tmpfile || " < " || console
  163.   system(edstr)
  164.   upto('nN', query("Are you sure you want to post this to Usenet y/n? ")) & {
  165.     if upto('yY', query("Save your draft article y/n? ")) then 
  166.       stop("Your article is saved in ", tmpfile)
  167.     else {
  168.       remove(tmpfile)
  169.       stop("Posting aborted.") 
  170.     }
  171.   }
  172.     # For inews, we supply the headers, inews supplies the .signature.
  173.  
  174.   if mode == "local" then mode := newslib || "inews -h"
  175.   else {
  176.     \sigfile & {
  177.       article := open(tmpfile, "a")
  178.       write(article, "--")  
  179.       every write(article, lookup(sigfile))
  180.     }
  181.     # To post via sendnews (mail), we prefix lines with 'N'.
  182.     # For rnews, don't force an immediate poll.
  183.  
  184.     case mode of {
  185.       "mail": {
  186.              mode ||:= " " || smarthost || "!rnews" 
  187.          outf := open(tmp2 := tempname(tmpdir), "w")
  188.          every write(outf, "N", lookup(tmpfile))
  189.          remove(tmpfile)
  190.          rename(tmp2, tmpfile)
  191.        }
  192.       "uux": mode ||:= " - -r " || smarthost || "!rnews"
  193.     }
  194.   }
  195.   mode ||:= " < " || tmpfile
  196.   (system(mode) = 0) & write("Article posted!")
  197.   remove(tmpfile)
  198. end
  199.  
  200.     # To parse the original article, we use case-insensitive
  201.     # matches on the headers.  The Reply-to and Followup-To
  202.     # headers usually appear later than From and Newsgroups, so
  203.     # they take precedence.  By usenet convention, we query
  204.     # the user if Followup-To on the original is `poster'.
  205.  
  206. procedure reply_headers(infile, art)
  207.   local fullname, address, quoter, date, id, subject, distribution
  208.   local group, refs
  209.  
  210.   every !infile ? {
  211.     tab(match("from: " | "reply-to: ", map(&subject))) & {
  212.       if find("<") then {
  213.     fullname := (trim(tab(upto('<'))) ~== "")
  214.     address := (move(1), tab(find(">")))
  215.       }
  216.       else {
  217.     address := trim(tab(upto('(') | 0))
  218.     fullname := (move(1), tab(find(")")))
  219.       }
  220.       quoter := (\fullname | address)
  221.     }
  222.     tab(match("date: ", map(&subject))) & date := tab(0)
  223.     tab(match("message-id: ", map(&subject))) & id := tab(0)
  224.     tab(match("subject: ", map(&subject))) & subject := tab(0)
  225.     tab(match("distribution: ", map(&subject))) & distribution := tab(0)
  226.     tab(match("newsgroups: " | "followup-to: ", map(&subject))) & 
  227.       group := tab(0)
  228.     tab(match("references: ", map(&subject))) & refs := tab(0)
  229.     (\quoter & *&subject = 0) & {
  230.       find("poster", group) & {
  231.     write(quoter, " has requested followups by email.")
  232.     upto('yY', query("Do you want to abort this posting y/n? ")) & {
  233.       remove(tmpfile)
  234.       stop("Posting aborted.")
  235.     }
  236.     group := &null
  237.       }
  238.       write(art, "Newsgroups: ", \group | 
  239.     validate(\opts["n"] | query("Newsgroups: "), "active"))
  240.       write(art, "Subject: ", \opts["s"] | \subject | query("Subject: "))
  241.       \distribution | distribution := validate(\opts["d"], "distributions") &
  242.         write(art, "Distribution: ", distribution)
  243.       write(art, "References: ", (\refs ||:= " ") | "", id)
  244.       every write(art, req_headers())
  245.       write(art, "In-reply-to: ", quoter, "'s message of ", date)
  246.       write(art, "\nIn ", id, ", ", quoter, " writes:\n")
  247.       return 
  248.     }
  249.   }
  250. end
  251.  
  252.     # We need a unique message-id, and a date in RFC822 format.
  253.     # Easy with UNIX systems that support `date -u'; with the
  254.     # others, we leave the local timezone.  The first inews site
  255.     # will correct it.
  256.  
  257. procedure req_headers()
  258.   local uniq, date, month, day, time, zone, year
  259.  
  260.   uniq := "<"
  261.   &date || &clock ? while tab(upto(&digits)) do uniq ||:= tab(many(&digits))
  262.   uniq ||:= "@" || sysname || domain || ">"
  263.   if tz == "unix" then {
  264.     date := pipe("date -u", "date")
  265.     date ? {
  266.      month := (tab(find(" ") + 1), tab(many(&letters)))
  267.      day := (tab(upto(&digits)), tab(many(&digits)))
  268.      time := (tab(upto(&digits++':')), tab(many(&digits++':')))
  269.      zone := (tab(upto(&ucase)), tab(many(&ucase)))
  270.      year := (tab(upto(&digits)+ 2), tab(0))
  271.     }
  272.     date := day || " " || month || " " || year ||  " " || time || " " || zone
  273.   }
  274.   else {
  275.     &dateline ? {
  276.       month := left((tab(find(" ")+1), tab(many(&letters))), 3) || " "
  277.       date := (tab(upto(&digits)), tab(many(&digits))) || " " || month
  278.       date ||:= (tab(upto(&digits)), right(tab(many(&digits)), 2))
  279.     }
  280.     date ||:= " " || &clock || " " || tz
  281.   }
  282.   mode ~== "local" & suspend "Message-ID: " || uniq
  283.   suspend "Date: " || date 
  284.   \org & suspend "Organization: " || org
  285.   \opts["f"] & return "Followup-To: " || ((opts["f"] == "poster") | 
  286.     validate(opts["f"], "active"))
  287. end
  288.  
  289.     # Richard Goerwitz's generator.
  290.  
  291. procedure tempname(dir)
  292.   local temp_name
  293.  
  294.   every temp_name := dir || "article." || right(1 to 999,3,"0") do {
  295.     close(open(temp_name)) & next
  296.     suspend \temp_name
  297.   }
  298. end
  299.  
  300.     # On systems with pipes, pipe() will read from the first
  301.     # successful command of the list given as arguments.
  302.  
  303. procedure pipe(cmd[])
  304.   local inf, got
  305.  
  306.   initial find("pipes" | "compiled", &features) | stop("No pipes.")
  307.   while inf := open("(" || pop(cmd) || ") 2>&1", "pr") do {
  308.     got := []
  309.     every put(got, !inf)
  310.     close(inf) = 0 & {
  311.       suspend !got 
  312.       break
  313.     }
  314.   }
  315. end
  316.  
  317.     # The dirty work of reading from a file.
  318.  
  319. procedure lookup(what)
  320.   local inf
  321.  
  322.   inf := open(what, "r") | fail
  323.   suspend !inf 
  324.   close(inf)
  325. end
  326.  
  327.     # Query opens stdin because the system call to the editor
  328.     # redirects input.  The optional parameter is a default
  329.     # response if the user answers with <return>.
  330.  
  331. procedure query(prompt, def)
  332.   local ans
  333.   static stdin
  334.  
  335.   initial stdin := open(console)
  336.   writes(prompt)
  337.   ans := read(stdin)
  338.   return (*ans = 0 & \def) | ans
  339. end
  340.  
  341.     # A quick and dirty kludge.  Validate() builds a sorted list.
  342.     # When an element is found, it is popped and the search moves
  343.     # to the next item.  The procedure assumes the file is also
  344.     # sorted.
  345.  
  346. procedure validate(what, where)
  347.   local valid, stuff, sf, a
  348.  
  349.   mode ~== "local" & return what
  350.   valid := &letters ++ '.-' ++ &digits
  351.   stuff := []
  352.   what ? while tab(upto(valid)) do put(stuff,tab(many(valid))) 
  353.   sf := open(newslib || where) | {
  354.     remove(tmpfile)
  355.     stop("post: cannot open ", newslib || where)
  356.   }
  357.   stuff := sort(stuff)
  358.   a := pop(stuff)
  359.   every !sf ? match(a) & (a := pop(stuff)) | return what
  360.   remove(tmpfile)
  361.   stop("`", a, "' is not in ", newslib || where)
  362. end
  363.